<?php

namespace CMS\Model;

use Think\Model;

use CMS\Enum\DocType;
use CMS\Enum\DocStatus;

/**
 * 文档基础模型
 */
class Document extends Model
{
    protected $name = 'cms_doument';
    protected $tableName = 'document';

    public $page = '';
    
    public function _initialize()
    {
        $this->addBothRule('name', 'english', '文档标识不合法')
            ->addBothRule('name', '0,30', '文档标识长度不合法', 'length')
            ->addBothRule('name', '', '标识已经存在', 'unique')
            ->addBothRule('title', 'require', '标题不能为空')
            ->addBothRule('title', '1,80', '标题长度不能超过80个字符', 'length')
            ->addBothRule('category_id', 'check_category', '该分类不允许发布内容', 'function')
            ->addBothRule('level', '/^[\d]+$/', '优先级只能填正整数')
            ->addBothRule('description', '0,140', '简介长度不能超过140个字符', 'length')
            ->addInsertRule('category_id', 'require', '分类不能为空')
            ->addInsertRule('category_id,type', 'check_category', '内容类型不正确', 'function')
            ->addInsertRule('model_id,category_id,pid', 'check_category_model', '该分类没有绑定当前模型', 'function')
            ->addUpdateRule('category_id', 'require', '分类不能为空', 'regex', self::Existed);
        
        $this->addInsertAuto('uid', 'session', 'function', 'user_auth.uid')
            ->addAuto('attach', 0)
            ->addAuto('view', 0)
            ->addAuto('comment', 0)
            ->addAuto('extend', 0)
            ->addAuto('create_time', 'getCreateTime', 'callback')
            //->addAuto('reply_time', NOW_TIME)
            ->addBothAuto('title', 'htmlspecialchars', 'function')
            ->addBothAuto('description', 'htmlspecialchars', 'function')
            ->addBothAuto('link_id', 'getLink', 'callback')
            ->addBothAuto('position', 'getPosition', 'callback')
            ->addBothAuto('root', 'getRoot', 'callback')
            ->addBothAuto('update_time', NOW_TIME)
            ->addBothAuto('deadline', 'strtotime', 'function')
            ->addBothAuto('status', 'getStatus', 'callback');
    }

    /**
     * 获取文档列表
     * 
     * @param integer $category 分类ID
     * @param string $order 排序规则
     * @param integer $status 状态
     * @param boolean $count 是否返回总数
     * @param string $field 字段 true-所有字段
     * @return array 文档列表
     */
    public function lists($category, $order = '`id` DESC', $status = 1, $field = true)
    {
        $map = $this->listMap($category, $status);
        return $this->field($field)
            ->where($map)
            ->order($order)
            ->select();
    }

    /**
     * 计算列表总数
     * 
     * @param number $category
     *            分类ID
     * @param integer $status
     *            状态
     * @return integer 总数
     */
    public function listCount($category, $status = 1)
    {
        $map = $this->listMap($category, $status);
        return $this->where($map)->count('id');
    }

    /**
     * 获取详情页数据
     * 
     * @param integer $id
     *            文档ID
     * @return array 详细数据
     */
    public function detail($id)
    {
        /* 获取基础数据 */
        $info = $this->field(true)->find($id);
        if (! $info) {
            $this->error = '文档不存在';
            return false;
        } elseif (! (is_array($info)) || in_array($info['status'], [DocStatus::DELETED, DocStatus::FORBIDDEN])) {
            $this->error = '文档被禁用或已删除！';
            return false;
        }
        /* 获取模型数据 */
        $logic = $this->logic($info['model_id']);
        $detail = $logic->detail($id); // 获取指定ID的数据
        if (! $detail) {
            $this->error = $logic->getError();
            return false;
        }
        return array_merge($info, $detail);
    }

    /**
     * 返回前一篇文档信息
     * 
     * @param array $info
     *            当前文档信息
     * @return array
     */
    public function prev($info)
    {
        $map = array(
            'id' => array('lt', $info['id']),
            'pid' => 0,
            'category_id' => $info['category_id'],
            'status' => 1,
            'create_time' => array('lt', NOW_TIME),
            '_string' => 'deadline = 0 OR deadline > ' . NOW_TIME
        );
        
        /* 返回前一条数据 */
        return $this->field(true)
            ->where($map)
            ->order('id DESC')
            ->find();
    }

    /**
     * 获取下一篇文档基本信息
     * 
     * @param array $info 当前文档信息
     * @return array
     */
    public function next($info)
    {
        $map = array(
            'id' => array('gt', $info['id']),
            'pid' => 0,
            'category_id' => $info['category_id'],
            'status' => 1,
            'create_time' => array('lt', NOW_TIME),
            '_string' => 'deadline = 0 OR deadline > ' . NOW_TIME
        );
        
        /* 返回下一条数据 */
        return $this->field(true)
            ->where($map)
            ->order('id')
            ->find();
    }

    /**
     * 后台新增或更新一个文档
     * 
     * @param array $data
     *            手动传入的数据
     * @return boolean fasle 失败 ， int 成功 返回完整的数据
     */
    public function update($data = null)
    {
        /* 检查文档类型是否符合要求 */
        $res = $this->checkDocumentType(I('type', DocType::TOPIC), I('pid'));
        if (! $res['status']) {
            $this->error = $res['info'];
            return false;
        }
        
        /* 获取数据对象 前台后台稍微有点不同,前台需要过滤 'pos,display' */
        $data = $this->token(false)->create($data);
        if (empty($data)) {
            return false;
        }
        
        /* 添加或新增基础内容 */
        if (empty($data['id'])) { // 新增数据
            $id = $this->add(); // 添加基础内容
            if (! $id) {
                $this->error = '新增基础内容出错！';
                return false;
            }
        } else { // 更新数据
            $status = $this->save(); // 更新基础内容
            if (false === $status) {
                $this->error = '更新基础内容出错！';
                return false;
            }
        }
        
        /* 添加或新增扩展内容 */
        $logic = logic($data['model_id']);
        $logic->checkModelAttr($data['model_id']);
        if (! $logic->update($id)) {
            if (isset($id)) { // 新增失败，删除基础数据
                $this->delete($id);
            }
            $this->error = $logic->getError();
            return false;
        }
        
        hook('documentSaveComplete', array(
            'model_id' => $data['model_id']
        ));
        
        // 行为记录
        if ($id) {
            action_log('add_document', 'document', $id, UID);
        }
        
        // 内容添加或更新完成
        return $data;
    }

    /**
     * 获取段落列表
     * 
     * @param integer $id 文档ID
     * @param integer $page 显示页码
     * @param boolean $field 查询字段
     * @param boolean $logic 是否查询模型数据
     * @return array
     */
    public function part($id, $page = 1, $field = true, $logic = true)
    {
        $map = array(
            'status' => 1,
            'pid' => $id,
            'type' => 3
        );
        $info = $this->field($field)
            ->where($map)
            ->page($page, 10)
            ->order('id')
            ->select();
        if (! $info) {
            $this->error = '该文档没有段落！';
            return false;
        }
        
        /* 不获取内容详情 */
        if (! $logic) {
            return $info;
        }
        
        /* 获取内容详情 */
        $model = $logic = array();
        foreach ($info as $value) {
            $model[$value['model_id']][] = $value['id'];
        }
        foreach ($model as $model_id => $ids) {
            $data = $this->logic($model_id)->lists($ids);
            $logic += $data;
        }
        
        /* 合并数据 */
        foreach ($info as &$value) {
            $value = array_merge($value, $logic[$value['id']]);
        }
        
        return $info;
    }

    /**
     * 获取指定文档的段落总数
     * 
     * @param number $id 段落ID
     * @return number 总数
     */
    public function partCount($id)
    {
        $map = array(
            'status' => 1,
            'pid' => $id,
            'type' => 3
        );
        return $this->where($map)->count('id');
    }

    /**
     * 获取推荐位数据列表
     * 
     * @param number $pos 推荐位 1-列表推荐，2-频道页推荐，4-首页推荐
     * @param number $category 分类ID
     * @param number $limit 列表行数
     * @param boolean $field 查询字段
     * @return array 数据列表
     */
    public function position($pos, $category = null, $limit = null, $field = true)
    {
        $map = $this->listMap($category, 1, $pos);
        
        /* 设置列表数量 */
        is_numeric($limit) && $this->limit($limit);
        
        /* 读取数据 */
        return $this->field($field)
            ->where($map)
            ->select();
    }
    
    /**
     * 获取数据状态
     * 
     * @return integer 数据状态
     */
    protected function getStatus()
    {
        $id = I('post.id');
        if (empty($id)) { // 新增
            $cate = I('post.category_id');
            $check = M('Category')->getFieldById($cate, 'check');
            $status = $check ? 2 : 1;
        } else { // 更新
            $status = $this->getFieldById($id, 'status');
            // 编辑草稿改变状态
            if ($status == 3) {
                $status = 1;
            }
        }
        return $status;
    }

    /**
     * 获取根节点id
     * 
     * @return integer 数据id
     */
    protected function getRoot()
    {
        $pid = I('post.pid');
        if ($pid == 0) {
            return 0;
        }
        $p_root = $this->getFieldById($pid, 'root');
        return $p_root == 0 ? $pid : $p_root;
    }

    /**
     * 获取扩展模型对象
     * 
     * @param integer $model 模型编号
     * @return object 模型对象
     */
    private function logic($model)
    {
        $name = parse_name(get_document_model($model, 'name'), 1);
        $class = '\\CMS\\Model\\Content\\' . $name;
        return new $class($name);
    }

    /**
     * 设置where查询条件
     * 
     * @param number $category 分类ID
     * @param integer $status 状态
     * @param number $pos 推荐位
     * @return array 查询条件
     */
    private function listMap($category, $status = 1, $pos = null)
    {
        /* 设置状态 */
        $map = array(
            'status' => $status,
            'pid' => 0
        );
        
        /* 设置分类 */
        if (! is_null($category)) {
            if (is_numeric($category)) {
                $map['category_id'] = $category;
            } else {
                $map['category_id'] = array(
                    'in',
                    str2arr($category)
                );
            }
        }
        
        $map['create_time'] = array(
            'lt',
            NOW_TIME
        );
        $map['_string'] = 'deadline = 0 OR deadline > ' . NOW_TIME;
        
        /* 设置推荐位 */
        if (is_numeric($pos)) {
            $map[] = "position & {$pos} = {$pos}";
        }
        
        return $map;
    }
    
    /**
     * 保存为草稿
     * 
     * @return array 完整的数据， false 保存出错
     * @author huajie <banhuajie@163.com>
     */
    public function autoSave()
    {
        $post = I('post.');
        
        /* 检查文档类型是否符合要求 */
        $res = $this->checkDocumentType(I('type', DocType::TOPIC), I('pid'));
        if (! $res['status']) {
            $this->error = $res['info'];
            return false;
        }
        
        // 触发自动保存的字段
        $save_list = array(
            'name',
            'title',
            'description',
            'position',
            'link_id',
            'cover_id',
            'deadline',
            'create_time',
            'content'
        );
        foreach ($save_list as $value) {
            if (! empty($post[$value])) {
                $if_save = true;
                break;
            }
        }
        
        if (! $if_save) {
            $this->error = '您未填写任何内容';
            return false;
        }
        
        // 重置自动验证
        $this->_validate = array(
            array(
                'name',
                '/^[a-zA-Z]\w{0,39}$/',
                '文档标识不合法',
                self::VALUE_VALIDATE,
                'regex',
                self::MODEL_BOTH
            ),
            array(
                'name',
                '',
                '标识已经存在',
                self::VALUE_VALIDATE,
                'unique',
                self::MODEL_BOTH
            ),
            array(
                'title',
                '1,80',
                '标题长度不能超过80个字符',
                self::VALUE_VALIDATE,
                'length',
                self::MODEL_BOTH
            ),
            array(
                'description',
                '1,140',
                '简介长度不能超过140个字符',
                self::VALUE_VALIDATE,
                'length',
                self::MODEL_BOTH
            ),
            array(
                'category_id',
                'require',
                '分类不能为空',
                self::MUST_VALIDATE,
                'regex',
                self::MODEL_BOTH
            ),
            array(
                'category_id',
                'check_category',
                '该分类不允许发布内容',
                self::EXISTS_VALIDATE,
                'function',
                self::MODEL_UPDATE
            ),
            array(
                'category_id,type',
                'check_category',
                '内容类型不正确',
                self::MUST_VALIDATE,
                'function',
                self::MODEL_INSERT
            ),
            array(
                'model_id,pid,category_id',
                'check_catgory_model',
                '该分类没有绑定当前模型',
                self::MUST_VALIDATE,
                'function',
                self::MODEL_INSERT
            ),
            array(
                'deadline',
                '/^\d{4,4}-\d{1,2}-\d{1,2}(\s\d{1,2}:\d{1,2}(:\d{1,2})?)?$/',
                '日期格式不合法,请使用"年-月-日 时:分"格式,全部为数字',
                self::VALUE_VALIDATE,
                'regex',
                self::MODEL_BOTH
            ),
            array(
                'create_time',
                '/^\d{4,4}-\d{1,2}-\d{1,2}(\s\d{1,2}:\d{1,2}(:\d{1,2})?)?$/',
                '日期格式不合法,请使用"年-月-日 时:分"格式,全部为数字',
                self::VALUE_VALIDATE,
                'regex',
                self::MODEL_BOTH
            )
        );
        $this->_auto[] = array(
            'status',
            '3',
            self::MODEL_BOTH
        );
        
        if (! ($data = $this->create())) {
            return false;
        }
        
        /* 添加或新增基础内容 */
        if (empty($data['id'])) { // 新增数据
            $id = $this->add(); // 添加基础内容
            if (! $id) {
                $this->error = '新增基础内容出错！';
                return false;
            }
            $data['id'] = $id;
        } else { // 更新数据
            $status = $this->save(); // 更新基础内容
            if (false === $status) {
                $this->error = '更新基础内容出错！';
                return false;
            }
        }
        
        /* 添加或新增扩展内容 */
        $logic = logic($data['model_id']);
        if (! $logic->autoSave($id)) {
            if (isset($id)) { // 新增失败，删除基础数据
                $this->delete($id);
            }
            $this->error = $logic->getError();
            return false;
        }
        
        // 内容添加或更新完成
        return $data;
    }

    /**
     * 创建时间不写则取当前时间
     * 
     * @return int 时间戳
     * @author huajie <banhuajie@163.com>
     */
    protected function getCreateTime()
    {
        $create_time = I('post.create_time');
        return $create_time ? strtotime($create_time) : NOW_TIME;
    }

    /**
     * 检查标识是否已存在(只需在同一根节点下不重复)
     * 
     * @param string $name            
     * @return true无重复，false已存在
     * @author huajie <banhuajie@163.com>
     */
    protected function checkName()
    {
        $name = I('post.name');
        $category_id = I('post.category_id', 0);
        $id = I('post.id', 0);
        
        $map = array(
            'name' => $name,
            'id' => array(
                'neq',
                $id
            ),
            'status' => array(
                'neq',
                - 1
            )
        );
        
        $category = get_category($category_id);
        if ($category['pid'] == 0) {
            $map['category_id'] = $category_id;
        } else {
            $parent = get_parent_category($category['id']);
            $root = array_shift($parent);
            $map['category_id'] = array(
                'in',
                D("Category")->getChildrenId($root['id'])
            );
        }
        
        $res = $this->where($map)->getField('id');
        if ($res) {
            return false;
        }
        return true;
    }

    /**
     * 生成不重复的name标识
     * 
     * @author huajie <banhuajie@163.com>
     */
    private function generateName()
    {
        $str = 'abcdefghijklmnopqrstuvwxyz0123456789'; // 源字符串
        $min = 10;
        $max = 39;
        $name = false;
        while (true) {
            $length = rand($min, $max); // 生成的标识长度
            $name = substr(str_shuffle(substr($str, 0, 26)), 0, 1); // 第一个字母
            $name .= substr(str_shuffle($str), 0, $length);
            // 检查是否已存在
            $res = $this->getFieldByName($name, 'id');
            if (! $res) {
                break;
            }
        }
        return $name;
    }

    /**
     * 生成推荐位的值
     * 
     * @return number 推荐位
     * @author huajie <banhuajie@163.com>
     */
    protected function getPosition()
    {
        $position = I('post.position');
        if (! is_array($position)) {
            return 0;
        } else {
            $pos = 0;
            foreach ($position as $key => $value) {
                $pos += $value; // 将各个推荐位的值相加
            }
            return $pos;
        }
    }

    /**
     * 删除状态为-1的数据（包含扩展模型）
     * 
     * @return true 删除成功， false 删除失败
     * @author huajie <banhuajie@163.com>
     */
    public function remove()
    {
        // 查询假删除的基础数据
        if (is_administrator()) {
            $map = array(
                'status' => - 1
            );
        } else {
            $cate_ids = AuthGroupModel::getAuthCategories(UID);
            $map = array(
                'status' => - 1,
                'category_id' => array(
                    'IN',
                    trim(implode(',', $cate_ids), ',')
                )
            );
        }
        $base_list = $this->where($map)
            ->field('id,model_id')
            ->select();
        // 删除扩展模型数据
        $base_ids = array_column($base_list, 'id');
        // 孤儿数据
        $orphan = get_stemma($base_ids, $this, 'id,model_id');
        
        $all_list = array_merge($base_list, $orphan);
        foreach ($all_list as $key => $value) {
            $logic = logic($value['model_id']);
            $logic->delete($value['id']);
        }
        
        // 删除基础数据
        $ids = array_merge($base_ids, (array) array_column($orphan, 'id'));
        if (! empty($ids)) {
            $res = $this->where(array(
                'id' => array(
                    'IN',
                    trim(implode(',', $ids), ',')
                )
            ))->delete();
        }
        
        return $res;
    }

    /**
     * 获取链接id
     * 
     * @return int 链接对应的id
     * @author huajie <banhuajie@163.com>
     */
    protected function getLink()
    {
        $link = I('post.link_id');
        if (empty($link)) {
            return 0;
        } else 
            if (is_numeric($link)) {
                return $link;
            }
        $res = D('Url')->update(array(
            'url' => $link
        ));
        return $res['id'];
    }


    /**
     * 检查指定文档下面子文档的类型
     * 
     * @param intger $type
     *            子文档类型
     * @param intger $pid
     *            父文档类型
     * @return array 键值：status=>是否允许（0,1），'info'=>提示信息
     * @author huajie <banhuajie@163.com>
     */
    public function checkDocumentType($type = null, $pid = null)
    {
        $res = array(
            'status' => 1,
            'info' => ''
        );
        if (empty($type)) {
            return array(
                'status' => 0,
                'info' => '文档类型不能为空'
            );
        }
        if (empty($pid)) {
            return $res;
        }
        // 查询父文档的类型
        $ptype = is_numeric($pid) ? $this->getFieldById($pid, 'type') : $this->getFieldByName($pid, 'type');
        // 父文档为目录时
        switch ($ptype) {
            case DocType::CATELOG: // 目录
            case DocType::TOPIC: // 主题
                break;
            case DocType::SECTION: // 段落
                return array(
                    'status' => 0,
                    'info' => '段落下面不允许再添加子内容'
                );
            default:
                return array(
                    'status' => 0,
                    'info' => '父文档类型不正确'
                );
        }
        return $res;
    }
}
